mod tobcoord;
mod tobmux;
mod tobmuxregister;

pub use tobcoord::TOBCoord;
use tobmux::{TOBMux, TOBMuxConnector};
use tobmuxregister::TOBMuxRegister;

const INDEX_SIZE: usize = 128;

const B_TO_H_MUX_SIZE: usize = 8;
const B_TO_H_MUX_COUNT: usize = 16;

const H_TO_V_MUX_SIZE: usize = 8;
const H_TO_V_MUX_COUNT: usize = 16;

const V_TO_T_MUX_SIZE: usize = 2;
const V_TO_T_MUX_COUNT: usize = 64;

#[derive(Clone)]
pub struct TOB {
    b_to_h_muxs: Vec<TOBMux>,
    h_to_v_muxs: Vec<TOBMux>,
    v_to_t_muxs: Vec<TOBMux>,
}

impl TOB {
    pub fn empty() -> Self {
        Self {
            b_to_h_muxs: vec![TOBMux::with_size(B_TO_H_MUX_SIZE); B_TO_H_MUX_COUNT],
            h_to_v_muxs: vec![TOBMux::with_size(H_TO_V_MUX_SIZE); H_TO_V_MUX_COUNT],
            v_to_t_muxs: vec![TOBMux::with_size(V_TO_T_MUX_SIZE); V_TO_T_MUX_COUNT],
        }
    }
}

impl TOB {
    pub fn b_index_map_t_index(&self, b_index: usize) -> Option<usize> {
        self.b_index_map_h_index(b_index).and_then(|h_index| {
            self.h_index_map_v_index(h_index).and_then(|v_index| {
                self.v_index_map_t_index(v_index)
            })
        })
    }

    pub fn b_index_map_h_index(&self, b_index: usize) -> Option<usize> {
        let b_info@(b_mux_idx, b_mux_inner_idx) = TOB::b_info(b_index);
        let h_mux_output_idx = self.b_to_h_muxs[b_mux_idx].index_map(b_mux_inner_idx)?;
        let h_index = TOB::b_info_and_h_output_idx_to_h_index(b_info, h_mux_output_idx);

        Some(h_index)
    }

    pub fn h_index_map_v_index(&self, h_index: usize) -> Option<usize> {
        let h_info@(h_mux_idx, h_mux_inner_idx) = TOB::h_info(h_index);
        let v_mux_output_idx = self.h_to_v_muxs[h_mux_idx].index_map(h_mux_inner_idx)?;
        let v_index = TOB::h_info_and_v_output_idx_to_v_index(h_info, v_mux_output_idx);

        Some(v_index)
    }

    pub fn v_index_map_t_index(&self, v_index: usize) -> Option<usize> {
        let v_info@(v_mux_idx, v_mux_inner_idx) = TOB::v_info(v_index); 
        let t_mux_output_idx = self.v_to_t_muxs[v_mux_idx].index_map(v_mux_inner_idx)?;
        let t_index = TOB::v_info_and_t_output_idx_to_t_index(v_info, t_mux_output_idx);
        
        Some(t_index)
    }
}

    
impl TOB {
    pub fn available_connectors(&self, b_index: usize) -> Vec<TOBConnector> {
        assert!(b_index < INDEX_SIZE);

        let mut cs = Vec::new();

        let b_index = b_index;
        let b_info@(b_mux_idx, b_mux_inner_idx) = TOB::b_info(b_index);
        
        for b_to_h_cs in self.b_to_h_muxs[b_mux_idx].available_connectors(b_mux_inner_idx) {
            
            let h_index = TOB::b_info_and_h_output_idx_to_h_index(b_info, b_to_h_cs.output_idx);
            let h_info@(h_mux_idx, h_mux_inner_idx) = TOB::h_info(h_index);
            
            for h_to_v_cs in self.h_to_v_muxs[h_mux_idx].available_connectors(h_mux_inner_idx) {

                let v_index = TOB::h_info_and_v_output_idx_to_v_index(h_info, h_to_v_cs.output_idx);
                let v_info@(v_mux_idx, v_mux_inner_idx) = TOB::v_info(v_index);

                for v_to_t_cs in self.v_to_t_muxs[v_mux_idx].available_connectors(v_mux_inner_idx) {
                    
                    let t_index = TOB::v_info_and_t_output_idx_to_t_index(v_info, v_to_t_cs.output_idx);
                    cs.push(TOBConnector::new(
                        b_to_h_cs.rc_clone(), 
                        h_to_v_cs.rc_clone(),
                        v_to_t_cs.rc_clone(),
                        b_index, t_index,
                    ))
                }
            }
        }

        cs
    }
}

impl TOB {
    pub fn b_to_h_register_nth(&self, b_mux_idx: usize, b_mux_inner_idx: usize) -> TOBMuxRegister {
        self.b_to_h_muxs[b_mux_idx].register(b_mux_inner_idx)
    }

    pub fn h_to_v_register_nth(&self, h_mux_idx: usize, h_mux_inner_idx: usize) -> TOBMuxRegister {
        self.h_to_v_muxs[h_mux_idx].register(h_mux_inner_idx)
    }

    pub fn v_to_t_register_nth(&self, v_mux_idx: usize, v_mux_inner_idx: usize) -> TOBMuxRegister {
        self.v_to_t_muxs[v_mux_idx].register(v_mux_inner_idx)
    }
}

impl TOB {
    pub fn b_to_h_register(&self, b_index: usize) -> TOBMuxRegister {
        let (b_mux_idx, b_mux_inner_idx) = TOB::b_info(b_index);
        self.b_to_h_register_nth(b_mux_idx, b_mux_inner_idx)
    }

    pub fn h_to_v_register(&self, h_index: usize) -> TOBMuxRegister {
        let (h_mux_idx, h_mux_inner_idx) = TOB::h_info(h_index);
        self.h_to_v_register_nth(h_mux_idx, h_mux_inner_idx)
    }

    pub fn v_to_t_register(&self, v_index: usize) -> TOBMuxRegister {
        let (v_mux_idx, v_mux_inner_idx) = TOB::v_info(v_index);
        self.v_to_t_register_nth(v_mux_idx, v_mux_inner_idx)
    }
}

impl TOB {
    fn b_info(b_index: usize) -> (usize, usize) {
        (
            b_index / 8,
            b_index % 8,
        )
    }

    fn b_info_and_h_output_idx_to_h_index(b_info: (usize, usize), h_output_idx: usize) -> usize {
        // Base + Bais
        let (b_mux_idx, _) = b_info;
        let h_index = b_mux_idx * 8 + h_output_idx;
        assert!(h_index < INDEX_SIZE);
        h_index
    }

    fn h_info(h_index: usize) -> (usize, usize) {
        if h_index >= 64 {
            (
                h_index % 8 + 8,
                (h_index - 64) / 8,
            )
        } else {
            (
                h_index % 8,
                h_index / 8,
            )
        }
    }

    fn h_info_and_v_output_idx_to_v_index(h_info: (usize, usize), v_output_idx: usize) -> usize {
        // Base + Bais
        let (h_mux_idx, _) = h_info;
        let v_index = h_mux_idx * 8 + v_output_idx;
        assert!(v_index < INDEX_SIZE);
        v_index
    }

    fn v_info(v_index: usize) -> (usize, usize) {
        if v_index >= 64 {
            (
                v_index - 64,
                1
            )
        } else {
            (
                v_index,
                0
            )
        }
    }

    fn v_info_and_t_output_idx_to_t_index(v_info: (usize, usize), t_output_idx: usize) -> usize {
        let t_index = v_info.0 + 64 * t_output_idx;
        assert!(t_index < INDEX_SIZE);
        t_index
    }
}

pub struct TOBConnector {
    b_to_h: TOBMuxConnector,
    h_to_v: TOBMuxConnector,
    v_to_t: TOBMuxConnector,

    pub b_index: usize,
    pub t_index: usize,
}

impl TOBConnector {
    pub fn new(b_to_h: TOBMuxConnector, h_to_v: TOBMuxConnector, v_to_t: TOBMuxConnector, b_index: usize, t_index: usize) -> Self {
        Self { b_to_h, h_to_v, v_to_t, b_index, t_index }
    }

    pub fn connect(&self) {
        self.b_to_h.connect();
        self.h_to_v.connect();
        self.v_to_t.connect();
    }
}

#[cfg(test)]
mod test {
    use super::*;
    
    #[test]
    fn test_available_output_indexes_1() {
        let tob = TOB::empty();

        for b_index in 0..128 {
            let cs = tob.available_connectors(b_index);
            println!("{b_index} : {}", cs.len());
            cs[0].connect();
        }
    }

    #[test]
    fn test_available_output_indexes_2() {
        let tob = TOB::empty();
        let cs = tob.available_connectors(0);
        assert_eq!(cs.len(), 128);
        cs[0].connect();

        let cs = tob.available_connectors(0);
        assert_eq!(cs.len(), 0);
    }

    #[test]
    fn test_b_index_map_t_index() {
        let tob = TOB::empty();

        assert_eq!(None, tob.b_index_map_t_index(0));
        
        let cs = tob.available_connectors(0);
        cs[17].connect();

        assert_eq!(Some(cs[17].t_index), tob.b_index_map_t_index(0));
    }

}